package com.hqd.ch03.v32.web.utils;


import com.hqd.ch03.utils.ClassUtils;
import com.hqd.ch03.v32.utils.StringUtils;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.MappingMatch;
import java.net.URLDecoder;
import java.nio.charset.UnsupportedCharsetException;

public class UrlPathHelper {
    public static final String PATH_ATTRIBUTE = UrlPathHelper.class.getName() + ".PATH";
    static final boolean servlet4Present =
            ClassUtils.hasMethod(HttpServletRequest.class, "getHttpServletMapping");
    private boolean alwaysUseFullPath = false;
    private boolean urlDecode = true;

    private boolean removeSemicolonContent = true;

    private String defaultEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING;


    public static String getResolvedLookupPath(ServletRequest request) {
        String lookupPath = (String) request.getAttribute(PATH_ATTRIBUTE);
        return lookupPath;
    }

    /**
     * 去除URL中的双斜杠
     *
     * @param path
     * @return
     */
    private static String getSanitizedPath(final String path) {
        int start = path.indexOf("//");
        if (start == -1) {
            return path;
        }
        char[] content = path.toCharArray();
        int slowIndex = start;
        for (int fastIndex = start + 1; fastIndex < content.length; fastIndex++) {
            if (content[fastIndex] != '/' || content[slowIndex] != '/') {
                content[++slowIndex] = content[fastIndex];
            }
        }
        return new String(content, 0, slowIndex + 1);
    }

    /**
     * 删除url中对应的;
     */
    private static String removeSemicolonContentInternal(String requestUri) {

        int semicolonIndex = requestUri.indexOf(';');
        if (semicolonIndex == -1) {
            return requestUri;
        }
        StringBuilder sb = new StringBuilder(requestUri);
        while (semicolonIndex != -1) {
            int slashIndex = sb.indexOf("/", semicolonIndex + 1);
            if (slashIndex == -1) {
                return sb.substring(0, semicolonIndex);
            }
            sb.delete(semicolonIndex, slashIndex);
            semicolonIndex = sb.indexOf(";", semicolonIndex);
        }
        return sb.toString();
    }

    public String resolveAndCacheLookupPath(HttpServletRequest request) {
        String lookupPath = getLookupPathForRequest(request);
        /**
         * 设置参数到request里面
         */
        request.setAttribute(PATH_ATTRIBUTE, lookupPath);
        return lookupPath;
    }

    public String getLookupPathForRequest(HttpServletRequest request) {
        /**
         * 处理url，删除对应的;、//等
         */
        String pathWithinApp = getPathWithinApplication(request);
        if (this.alwaysUseFullPath || skipServletPathDetermination(request)) {
            return pathWithinApp;
        }
        String rest = getPathWithinServletMapping(request, pathWithinApp);
        if (StringUtils.hasLength(rest)) {
            return rest;
        } else {
            return pathWithinApp;
        }
    }

    public String getPathWithinApplication(HttpServletRequest request) {
        /**
         * 调用include方法时，必须设置这些属性
         * javax.servlet.include.request_uri
         * javax.servlet.include.context_path
         * javax.servlet.include.servlet_path
         * javax.servlet.include.path_info
         * javax.servlet.include.query_string
         */
        String contextPath = getContextPath(request);
        String requestUri = getRequestUri(request);
        String path = getRemainingPath(requestUri, contextPath, true);
        if (path != null) {
            return (StringUtils.hasText(path) ? path : "/");
        } else {
            return requestUri;
        }
    }

    protected String getPathWithinServletMapping(HttpServletRequest request, String pathWithinApp) {
        String servletPath = getServletPath(request);
        String sanitizedPathWithinApp = getSanitizedPath(pathWithinApp);
        String path;

        if (servletPath.contains(sanitizedPathWithinApp)) {
            path = getRemainingPath(sanitizedPathWithinApp, servletPath, false);
        } else {
            path = getRemainingPath(pathWithinApp, servletPath, false);
        }

        if (path != null) {
            return path;
        } else {
            /**
             * 如果为空，则从PathInfo获取
             * PathInfo：请求路径的一部分，不属于 Context Path 或 Servlet Path。如果没有额外的路径，它要么是 null，
             * 要么是以'/'开头的字符串
             */
            String pathInfo = request.getPathInfo();
            if (pathInfo != null) {
                return pathInfo;
            }
            if (!this.urlDecode) {
                path = getRemainingPath(decodeInternal(request, pathWithinApp), servletPath, false);
                if (path != null) {
                    return pathWithinApp;
                }
            }
            return servletPath;
        }
    }

    public String getServletPath(HttpServletRequest request) {
        String servletPath = (String) request.getAttribute(WebUtils.INCLUDE_SERVLET_PATH_ATTRIBUTE);
        if (servletPath == null) {
            servletPath = request.getServletPath();
        }
        return servletPath;
    }

    public String getContextPath(HttpServletRequest request) {
        /**
         * 是否是include
         */
        String contextPath = (String) request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE);
        /**
         * 不是则正常获取上下文路径
         */
        if (contextPath == null) {
            contextPath = request.getContextPath();
        }
        if (StringUtils.matchesCharacter(contextPath, '/')) {
            contextPath = "";
        }
        return decodeRequestString(request, contextPath);
    }

    /**
     * 是否是Servlet4，如果是则调用对应方法进行匹配
     *
     * @param request
     * @return
     */
    private boolean skipServletPathDetermination(HttpServletRequest request) {
        if (servlet4Present) {
            return Servlet4Delegate.skipServletPathDetermination(request);
        }
        return false;
    }

    public String getRequestUri(HttpServletRequest request) {
        String uri = (String) request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE);
        if (uri == null) {
            uri = request.getRequestURI();
        }
        return decodeAndCleanUriString(request, uri);
    }

    private String decodeAndCleanUriString(HttpServletRequest request, String uri) {
        uri = removeSemicolonContent(uri);
        uri = decodeRequestString(request, uri);
        uri = getSanitizedPath(uri);
        return uri;
    }

    public String removeSemicolonContent(String requestUri) {
        return (this.removeSemicolonContent ?
                removeSemicolonContentInternal(requestUri) : removeJsessionid(requestUri));
    }

    /**
     * 删除jsessionid
     *
     * @param requestUri
     * @return
     */
    private String removeJsessionid(String requestUri) {
        String key = ";jsessionid=";
        int index = requestUri.toLowerCase().indexOf(key);
        if (index == -1) {
            return requestUri;
        }
        String start = requestUri.substring(0, index);
        for (int i = index + key.length(); i < requestUri.length(); i++) {
            char c = requestUri.charAt(i);
            if (c == ';' || c == '/') {
                return start + requestUri.substring(i);
            }
        }
        return start;
    }

    public String decodeRequestString(HttpServletRequest request, String source) {
        if (this.urlDecode) {
            /**
             * 转成ISO-8859-1编码
             */
            return decodeInternal(request, source);
        }
        return source;
    }

    private String decodeInternal(HttpServletRequest request, String source) {
        String enc = determineEncoding(request);
        try {
            return UriUtils.decode(source, enc);
        } catch (UnsupportedCharsetException ex) {
            ex.printStackTrace();
            return URLDecoder.decode(source);
        }
    }

    protected String getDefaultEncoding() {
        return this.defaultEncoding;
    }

    protected String determineEncoding(HttpServletRequest request) {
        String enc = request.getCharacterEncoding();
        if (enc == null) {
            enc = getDefaultEncoding();
        }
        return enc;
    }

    private String getRemainingPath(String requestUri, String mapping, boolean ignoreCase) {
        int index1 = 0;
        int index2 = 0;
        for (; (index1 < requestUri.length()) && (index2 < mapping.length()); index1++, index2++) {
            char c1 = requestUri.charAt(index1);
            char c2 = mapping.charAt(index2);
            if (c1 == ';') {
                index1 = requestUri.indexOf('/', index1);
                if (index1 == -1) {
                    return null;
                }
                c1 = requestUri.charAt(index1);
            }
            if (c1 == c2 || (ignoreCase && (Character.toLowerCase(c1) == Character.toLowerCase(c2)))) {
                continue;
            }
            return null;
        }
        /**
         * 不完全匹配
         */
        if (index2 != mapping.length()) {
            return null;
        } else if (index1 == requestUri.length()) {//完全匹配
            return "";
        } else if (requestUri.charAt(index1) == ';') {//如果最后一个是; ，则只有前面的部分
            index1 = requestUri.indexOf('/', index1);
        }
        return (index1 != -1 ? requestUri.substring(index1) : "");
    }

    private static class Servlet4Delegate {
        public static boolean skipServletPathDetermination(HttpServletRequest request) {
            HttpServletMapping mapping = (HttpServletMapping) request.getAttribute(RequestDispatcher.INCLUDE_MAPPING);
            if (mapping == null) {
                mapping = request.getHttpServletMapping();
            }
            MappingMatch match = mapping.getMappingMatch();
            return (match != null && (!match.equals(MappingMatch.PATH) || mapping.getPattern().equals("/*")));
        }
    }
}
